﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using Microsoft.MSE.Catalog.Contracts;
using Microsoft.MSE.Catalog.Contracts.Services;
using Microsoft.MSE.Catalog.Module;
using Microsoft.MSE.Catalog.Module.Configuration;
using Microsoft.MSE.Catalog.Module.Services;

namespace Microsoft.MSE.Catalog.SelfHost
{
    /// <summary>
    /// Catalog Server
    /// </summary>
    public class CatalogServer
    {
        public ServiceHost serviceHost = null;
        private SqlConnection sConn;
        private string sNotificationAddress = null;
        private XmlElement sNotificationBindingXml = null;
        
        private delegate void StartServiceAsyncHandler(int retryInterval, int retryAttempts);

        public event EventHandler<StartServiceCompletedEventArgs> StartServiceCompleted;

        private readonly string _hostingExecutableName;
        private readonly string _hostingExecutableVersion;
        private readonly Arguments _arguments;
        private bool _cancelStartup;

        public CatalogServer() : this(null)
        {
        }

        public CatalogServer(string[] args)
        {
            Process process = Process.GetCurrentProcess();
            ProcessModule processModule = process.MainModule;
            Assembly entryAssembly = Assembly.GetEntryAssembly();
            AssemblyName entryAssemblyName = entryAssembly.GetName();
            Version entyAssemblyVersion = entryAssemblyName.Version;

            if (processModule == null)
            {
                throw new Exception("Process module should never be null.");
            }

            _hostingExecutableName = processModule.ModuleName;
            _hostingExecutableVersion = entyAssemblyVersion.ToString();
            _arguments = new Arguments(args);

            SettingsManager.Repository.SqlDatabase = _arguments.SqlDatabase;
            SettingsManager.Repository.SqlServer = _arguments.SqlServer;
        }

        public List<string> ErrorMessages
        {
            get { return _arguments.ErrorMessages; }
        }

        public List<string> WarningMessages
        {
            get { return _arguments.WarningMessages; }
        }

        public List<string> InformationMessages
        {
            get { return _arguments.InfomationMessages; }
        }

        public string HostName
        {
            get { return _hostingExecutableName; }
        }

        public string HostVersion
        {
            get { return _hostingExecutableVersion; }
        }

        public string Name
        {
            get { return "Microsoft (R) Managed Services Engine Catalog Server"; }
        }

        public string Header
        {
            get
            {
                StringBuilder buffer = new StringBuilder();
                buffer.AppendLine(Name);
                buffer.AppendLine("[Microsoft (R) Managed Services Engine, Version " + _hostingExecutableVersion + "]");
                buffer.AppendLine("Copyright (c) Microsoft Corporation. All rights reserved.");
                buffer.AppendLine();
                return buffer.ToString();
            }
        }

        public string Usage
        {
            get
            {
                StringBuilder buffer = new StringBuilder();

                //
                // Uses
                //
                buffer.AppendLine("Uses:");
                buffer.AppendLine();
                buffer.AppendLine(" - Console host for diagnostically running catalog services.");
                buffer.AppendLine();

                //
                // Options
                //
                buffer.AppendLine("Options:");
                buffer.AppendLine();
                buffer.AppendLine(
                    " /SqlServer:<SqlServer>     - SQL Server 2005/2008 instance where MSE Repository is deployed.");
                buffer.AppendLine(
                    " /SqlDatabase:<SqlDatabase> - SQL Server 2005/2008 database name of the MSE Repository.");
                buffer.AppendLine();

                //
                // Description
                //
                buffer.AppendLine("Description: " + HostName + " hosts catalog services.");
                buffer.AppendLine();

                //
                // Syntax
                //
                buffer.Append("Syntax: " + HostName + " [/SqlServer:<SqlServer>] [/SqlDatabase:<SqlDatabase>]");
                buffer.AppendLine();

                return buffer.ToString();
            }
        }

        public void Start()
        {
            //BOTTO - removed TraceHelper.InitializeTraceSource.  Hosting applications need to call it so
            //they can attach any additional trace listeners (Console, EventLog, etc) based on hosting scenario.

            TraceHelper.TraceInformation("Starting " + Name + "...");
            Trace.AutoFlush = true;

            StartServiceCompleted += delegate(object sender, StartServiceCompletedEventArgs e)
                                         {
                                             if (e.Error == null) return;

                                             if (e.Error is ApplicationException)
                                             {
                                                 Console.ForegroundColor = ConsoleColor.Red;
                                                 Console.WriteLine(e.Error.Message);
                                                 Console.ResetColor();
                                             }
                                             else
                                             {
                                                 Console.ForegroundColor = ConsoleColor.Red;
                                                 Console.WriteLine(e.Error);
                                                 Console.ResetColor();
                                             }
                                         };

            StartServiceAsync();
        }

        protected void OnStartServiceCompleted(StartServiceCompletedEventArgs e)
        {
            try
            {
                if (StartServiceCompleted != null)
                {
                    StartServiceCompleted(null, e);
                }
            }
            catch
            {
            }
        }

        public void StartServiceAsync()
        {
            _cancelStartup = false;
            Configurator.ReadConfig();

            int delay = Configurator.svcConfig.StartupRetryInterval;
            int attempts = Configurator.svcConfig.StartupRetryAttempts;
            StartServiceAsyncHandler hnd = InnerStartService;
            hnd.BeginInvoke(delay, attempts, delegate(IAsyncResult ar)
                                                 {
                                                     StartServiceCompletedEventArgs args;
                                                     try
                                                     {
                                                         StartServiceAsyncHandler state =
                                                             ar.AsyncState as StartServiceAsyncHandler;
                                                         if (state != null)
                                                             state.EndInvoke(ar);
                                                         args = new StartServiceCompletedEventArgs(null, _cancelStartup,
                                                                                                   null);
                                                     }
                                                     catch (Exception ex)
                                                     {
                                                         args = new StartServiceCompletedEventArgs(ex, _cancelStartup,
                                                                                                   null);
                                                     }
                                                     OnStartServiceCompleted(args);
                                                 }, hnd);
        }

        public void StartService()
        {
            _cancelStartup = false;
            Configurator.ReadConfig();

            InnerStartService(0, 0);
        }

        private void InnerStartService(int retryInterval, int retryAttempts)
        {
            sConn = new SqlConnection(Configurator.svcConfig.DBConnString);

            //if we can't connect to the repository, we are finished
            if (!TryConnectToRepository(retryInterval, retryAttempts))
            {
                throw new ApplicationException("Could not establish connection to the Repository.");
            }

            ServiceModel catalogSvc = new ServiceModel(Configurator.svcConfig.baseAddress);
            serviceHost = new ServiceHost(catalogSvc, Configurator.svcConfig.baseAddress);

            ServiceMetadataBehavior bv = new ServiceMetadataBehavior();
            bv.HttpGetEnabled = true;
            serviceHost.Description.Behaviors.Add(bv);

            foreach (ServiceEndpointElement endpoint in Configurator.svcConfig.serviceEndpoints)
            {
                Binding binding;
                Type contract;

                binding = BindingHelper.DeserializeBinding(endpoint.bindingXml);
                if (endpoint.address.AbsoluteUri.EndsWith("/mex"))
                {
                    contract = typeof (IMetadataExchange);
                }
                else if (endpoint.address.AbsoluteUri.EndsWith("/NotificationService"))
                {
                    contract = typeof (IChangeNotificationService);
                    sNotificationAddress = endpoint.address.AbsoluteUri;
                    sNotificationBindingXml = endpoint.bindingXml;

                    //store these for calling the notification service
                    RepositoryChangeManager.NotificationServiceAddress = sNotificationAddress;
                    RepositoryChangeManager.NotificationServiceBinding = endpoint.bindingXml;
                }
                else
                {
                    contract = typeof (IServiceModel);
                }

                TraceHelper.TraceInformation(SC.ComponentName + " adding endpoint address " + endpoint.address +
                                             " over " + endpoint.bindingXml.FirstChild.Name);
                binding.ReceiveTimeout = new TimeSpan(1, 0, 0, 0, 0); //TODO: Look at this
                serviceHost.AddServiceEndpoint(contract, binding, endpoint.address);
            }

            ServiceDebugBehavior sdbbv = null;
            foreach (IServiceBehavior serviceBehavior in serviceHost.Description.Behaviors)
            {
                if (serviceBehavior is ServiceDebugBehavior) sdbbv = serviceBehavior as ServiceDebugBehavior;
            }

            if (sdbbv == null)
            {
                sdbbv = new ServiceDebugBehavior();
                serviceHost.Description.Behaviors.Add(sdbbv);
            }

            sdbbv.HttpHelpPageEnabled = true;

            if (Configurator.svcConfig.DebugMode)
            {
                sdbbv.IncludeExceptionDetailInFaults = true;
            }

            // Add ServiceThrottlingBehavior
            ServiceThrottlingBehavior stb = SetupServiceCatalogThrottle(Configurator.svcConfig.serviceCatalogThrottle);
            if (null != stb)
            {
                ServiceThrottlingBehavior stdStb = serviceHost.Description.Behaviors.Find<ServiceThrottlingBehavior>();
                if (null != stdStb)
                {
                    serviceHost.Description.Behaviors.Remove<ServiceThrottlingBehavior>();
                }
                serviceHost.Description.Behaviors.Add(stb);
            }

            serviceHost.Open();

            if (Configurator.svcConfig.RepositoryChangeMonitor.Enabled)
            {
                TimeSpan polling = TimeSpan.FromSeconds(Configurator.svcConfig.RepositoryChangeMonitor.PollingInterval);
                TimeSpan expiration =
                    TimeSpan.FromMinutes(Configurator.svcConfig.RepositoryChangeMonitor.MessageExpiration);
                RepositoryChangeManager.StartMonitor(polling, expiration);
                TraceHelper.TraceInformation("Repository Change Monitoring is enabled.");
            }
            else
            {
                TraceHelper.TraceInformation("Repository Change Monitoring is disabled.");
            }
        }

        public void Stop()
        {
            _cancelStartup = true;
            RepositoryChangeManager.StopMonitor();

            if (serviceHost != null)
            {
                if (serviceHost.State != CommunicationState.Closed) serviceHost.Close();
            }
        }

        private bool TryConnectToRepository(int retryInterval, int retryAttempts)
        {
            bool result = false;
            bool autoclose = false;

            if (sConn.State == ConnectionState.Closed)
                autoclose = true;

            //attempts = 1 + # of retries configured. 0 retries = 1 attempt
            int attempts = retryAttempts <= 0 ? 1 : retryAttempts + 1;

            while (!_cancelStartup && attempts-- >= 0)
            {
                try
                {
                    sConn.Open();
                    result = true;
                    break;
                }
                catch (Exception ex)
                {
                    TraceHelper.TraceError("Unable to connect to repository. Exception: " + ex.Message);
                }

                //stop if there is no delay specified (only try once), or we have run out of attempts
                if (retryInterval <= 0 || attempts <= 0)
                {
                    TraceHelper.TraceError(
                        "MSE Catalog could not connect to Repository.  No further attempts will be made.");
                    break;
                }

                TraceHelper.TraceInformation(
                    "MSE Catalog will attempt to connect to Respository in {0} seconds. {1} attempt(s) remaining.",
                    retryInterval, attempts);

                //configuration value is in seconds, convert to msec for thread sleep.
                System.Threading.Thread.Sleep(retryInterval*1000);
            }

            if (result && autoclose)
                sConn.Close();

            return result;
        }

        private static ServiceThrottlingBehavior SetupServiceCatalogThrottle(
            ServiceCatalogThrottle serviceCatalogThrottle)
        {
            ServiceThrottlingBehavior stb = new ServiceThrottlingBehavior();
            stb.MaxConcurrentCalls = 0 == serviceCatalogThrottle.MaxConcurrentCalls
                                         ? 100
                                         : serviceCatalogThrottle.MaxConcurrentCalls;
            stb.MaxConcurrentSessions = 0 == serviceCatalogThrottle.MaxConcurrentSessions
                                            ? 100
                                            : serviceCatalogThrottle.MaxConcurrentSessions;
            stb.MaxConcurrentInstances = 0 == serviceCatalogThrottle.MaxConcurrentInstances
                                             ? Int32.MaxValue
                                             : serviceCatalogThrottle.MaxConcurrentInstances;
            return stb;
        }

        //private static void DumpConfig()
        //{
        //    CatalogSvcConfig cfg = new CatalogSvcConfig();
        //    cfg.baseAddress = new Uri("http://localhost:8080/ServiceCatalog");
        //    cfg.DBConnString = "dbconn";
        //    ServiceEndpointElement svcEndpoint = new ServiceEndpointElement();
        //    svcEndpoint.address = new Uri("http://localhost:8080/ServiceCatalog/Basic");
        //    svcEndpoint.bindingXml.InnerXml = "<binding/>";
        //    cfg.serviceEndpoints = new ServiceEndpointElement[] { svcEndpoint };

        //    DataContractSerializer ser = new DataContractSerializer(typeof(CatalogSvcConfig));

        //    StringBuilder sb = new StringBuilder();
        //    XmlWriter writer = XmlTextWriter.Create(sb);
        //    ser.WriteObject(writer, cfg);
        //    writer.Flush();

        //    //Configuration cfgStore = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

        //    Console.WriteLine(sb);

        //}

        //private static void DumpDBEventNotification()
        //{
        //    DBEventContext msg = new DBEventContext();
        //    msg.Event = DBEvents.EndpointsUpdated;
        //    DataContractSerializer ser = new DataContractSerializer(typeof(DBEventContext));

        //    StringBuilder sb = new StringBuilder();
        //    XmlWriter writer = XmlTextWriter.Create(sb);
        //    ser.WriteObject(writer, msg);
        //    writer.Flush();

        //    Console.WriteLine(sb);
        //}

        //private static void DumpAcceptEvent()
        //{
        //    AcceptEventMsg msg = new AcceptEventMsg();
        //    msg.AcceptEvent = new DBEventContext();
        //    msg.AcceptEvent.Event = DBEvents.EndpointsUpdated;

        //    XmlSerializer ser = new XmlSerializer(typeof(AcceptEventMsg));

        //    StringBuilder sb = new StringBuilder();
        //    XmlWriter writer = XmlTextWriter.Create(sb);
        //    ser.Serialize(writer, msg);
        //    writer.Flush();

        //    Console.WriteLine(sb);
        //}
    }
}